/**
* Catroid: An on-device visual programming system for Android devices
* Copyright (C) 2010-2014 The Catrobat Team
* (<http://developer.catrobat.org/credits>)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* An additional term exception under section 7 of the GNU Affero
* General Public License, version 3, is available at
* http://developer.catrobat.org/license_additional_term
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.catrobat.html5player.client;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.catrobat.html5player.client.common.Look;
import org.catrobat.html5player.client.common.LookData;
import org.catrobat.html5player.client.common.Sound;
import org.catrobat.html5player.client.common.SoundInfo;
import org.catrobat.html5player.client.scripts.BroadcastScript;
import org.catrobat.html5player.client.scripts.Script;
import org.catrobat.html5player.client.scripts.StartScript;
import org.catrobat.html5player.client.scripts.WhenScript;
import org.catrobat.html5player.client.threading.CatScheduler;
import org.catrobat.html5player.client.threading.CatThread;
import org.catrobat.html5player.client.threading.WaitCount;
import com.google.gwt.canvas.client.Canvas;
import com.google.gwt.canvas.dom.client.Context2d;
import com.google.gwt.canvas.dom.client.ImageData;
import com.google.gwt.core.client.JavaScriptException;
import com.google.gwt.dom.client.ImageElement;
import com.google.gwt.media.client.Audio;
import com.google.gwt.user.client.ui.Image;
public class Sprite {
private final String name;
private boolean isBackground = false;
private double volume;
private Look look;
private Image currentLook = null;
private Map<String, Look> looks;
private Map<String, Sound> sounds;
private Set<Script> scripts;
public Sprite(String name) {
this.name = name;
this.isBackground = false;
this.volume = 70;
this.looks = new LinkedHashMap<String, Look>();
this.sounds = new LinkedHashMap<String, Sound>();
this.scripts = new LinkedHashSet<Script>();
look = new Look(Stage.getInstance().getStageMiddleX(), Stage.getInstance().getStageMiddleY(), true);
}
public String getName() {
return this.name;
}
public boolean isBackground() {
return isBackground;
}
public void setBackground(boolean background) {
isBackground = background;
}
public Look getLook() {
return look;
}
public void addLookData(LookData lookData) {
if (lookData != null && !looks.containsKey(lookData.getName())) {
Look look = new Look(0.0f, 0.0f, true);
look.setLookData(lookData);
looks.put(lookData.getName(), look);
}
}
public Look getLook(String name) {
return looks.get(name);
}
public List<Look> getLooks() {
List<Look> looks = new ArrayList<Look>();
looks.addAll(this.looks.values());
return looks;
}
/**
*
*/
public ArrayList<String> getLookDataNames() {
return new ArrayList<String>(looks.keySet());
}
/**
*
*/
public ArrayList<LookData> getLookData() {
ArrayList <Look> looksList = new ArrayList<Look>(looks.values());
ArrayList<LookData> lookDataList = new ArrayList<LookData>();
for(Look look : looksList) {
LookData lookData = look.getLookData();
lookDataList.add(lookData);
}
return lookDataList;
}
/**
*
*/
public LookData getLookDataByName(String lookName) {
if(looks.containsKey(lookName)) {
return looks.get(lookName).getLookData();
}
return null;
}
public void addScript(Script script) {
if (script != null && !scripts.contains(script)) {
scripts.add(script);
}
}
// unused, only in sprite test
public Script getScript(String name) {
for (Script script : scripts) {
if (script.getName().equals(name)) {
return script;
}
}
return null;
}
/**
*
* @param position
* @return script
*/
public Script getScript(int position){
ArrayList<Script> scriptsList = new ArrayList<Script>(scripts);
if(position < getNumberOfScripts())
return scriptsList.get(position);
else
return null;
}
public int getScriptIndex(Script script){
ArrayList<Script> scriptsList = new ArrayList<Script>(scripts);
int index = 0;
for(Script s : scriptsList)
{
if(s.getId() == script.getId())
{
return index;
}
index++;
}
return -1;
}
public void addSound(SoundInfo soundInfo) {
if (soundInfo != null && !sounds.containsKey(soundInfo.getId())) {
Sound sound = new Sound();
sound.setSoundInfo(soundInfo);
this.sounds.put(soundInfo.getId(), sound);
}
}
public Sound getSound(String id) {
return sounds.get(id);
}
/**
*
*/
public void showLook() {
if (look == null)
{
return;
}
LookData lookData = look.getLookData();
if(lookData == null)
{
return;
}
currentLook = ImageHandler.get().getImage(lookData.getFilename());
if(currentLook == null)
return;
lookData.setWidth(currentLook.getWidth());
lookData.setHeight(currentLook.getHeight());
}
public void drawSprite() {
if(currentLook == null || !look.isVisible()) {
CatrobatDebug.info("current look = " + currentLook + "| visible = " + look.isVisible());
return;
}
long start = System.currentTimeMillis();
CatrobatDebug.debug("drawSprite: " + this.name + " - customename: " + look.getLookData().getFilename());
LookData lookData = look.getLookData();
double size = look.getSize();
double width = (double)lookData.getWidth() * size;
double height = (double)lookData.getHeight() * size;
if(look.getBrightnessValue() == 1){
Scene.get().drawImageJSNI(currentLook,
look.getXPosition(),
look.getYPosition(),
-width / 2,
-height / 2,
width,
height,
-look.getRotation(), // the MINUS is important because canvas positive rotation is clockwise
look.getAlphaValue());
} else {
Scene.get().drawImageBrightness(currentLook, look.getXPosition(), look.getYPosition(), -width /2, -height/2, width, height, -look.getRotation(), look.getAlphaValue(), look.getBrightnessValue());
}
CatrobatDebug.debug("drawSprite-execution took " + (System.currentTimeMillis() - start + " ms"));
CatrobatDebug.debug("z-Pos: " + look.getZPosition() + " : name: " + this.name);
}
public void run() {
CatrobatDebug.debug("Sprite: " + this.name + ".run() - add startscripts");
CatrobatDebug.debug("Number of scripts: " + this.getNumberOfScripts());
List<Script> scriptList = new ArrayList<Script>();
scriptList.addAll(scripts);
Collections.sort(scriptList);
Stage.getInstance().setCurrentSprite(this);
for (Script script : scriptList) {
//only add StartScripts to the scheduler
if(script instanceof StartScript) {
// CatrobatDebug.debug("script is no WhenScript or BroadcastScript, Sprite: " + this.name);
CatThread thread = new CatThread(this.getName() + script.getName(), script);
CatScheduler.get().schedule(thread);
}
else if (script instanceof WhenScript) {
//CatrobatDebug.debug("script '" + script.getName() + "' is a WhenScript or a BroadcastScript, Sprite: " + this.name);
}
// script.run();
}
}
public void startTapScripts() {
CatrobatDebug.debug("<<< Sprite: " + this.name + " startTapScripts() >>>");
Stage.getInstance().setCurrentSprite(this);
for (Script script : scripts) {
if (script.getType().equals(WhenScript.SCRIPT_TYPE)) {
WhenScript touchScript = (WhenScript) script;
if(CatScheduler.get().getThread(script.getExecutor()) != null) {
CatrobatDebug.debug("Already existing Thread with WhenScript: " + script.getName());
continue;
}
else {
CatrobatDebug.info("Add WhenScript to Scheduler");
}
if(touchScript.hasScriptFinished()) {
touchScript.resetWhenScript();
}
CatThread thread = new CatThread(this.getName() + script.getName(), touchScript);
CatScheduler.get().schedule(thread);
CatrobatDebug.info("script added to scheduler");
}
}
}
public void startScriptBroadcast(BroadcastScript script) {
CatThread thread = new CatThread(this.getName() + script.getName(), script);
CatScheduler.get().schedule(thread);
}
public void startScriptBroadcastWait(BroadcastScript script, WaitCount signaler) {
CatThread thread = new CatThread(this.getName() + script.getName(), script);
thread.signalFinishedExecution(signaler);
CatScheduler.get().schedule(thread);
}
/**
*
* @return number of scripts
*/
public int getNumberOfScripts() {
return scripts.size();
}
/**
*
* @param relativeX
* @param relativeY
* @return
*/
public boolean processOnTouch(int relativeX, int relativeY) {
//Create temp canvas with the current look only to determine click (alpha)
Canvas temp = Canvas.createIfSupported();
temp.setWidth(Scene.get().getCanvas().getCoordinateSpaceWidth()+ "px");
temp.setHeight(Scene.get().getCanvas().getCoordinateSpaceHeight()+ "px");
temp.setCoordinateSpaceWidth(Scene.get().getCanvas().getCoordinateSpaceWidth());
temp.setCoordinateSpaceHeight(Scene.get().getCanvas().getCoordinateSpaceHeight());
LookData lookData = look.getLookData();
if(lookData == null)
return false;
double size = look.getSize();
double width = (double)lookData.getWidth() * size;
double height = (double)lookData.getHeight() * size;
double x = -width / 2;
double y = -height / 2;
Context2d context = temp.getContext2d();
context.translate(look.getXPosition(), look.getYPosition());
context.rotate((-look.getRotation()) * Math.PI / 180);
context.drawImage((ImageElement)currentLook.getElement().cast(), x, y, width, height);
if(context.getImageData(relativeX, relativeY, 1, 1).getAlphaAt(0, 0) == 0){
return false;
}
if (currentLook == null || !look.isVisible())
return false;
double xPosition = look.getXPosition();
double yPosition = look.getYPosition();
double newSize = look.getSize();
LookData newLookData = look.getLookData();
double widthHalf = ((double)newLookData.getWidth() * newSize) / 2;
double heightHalf = ((double)newLookData.getHeight() * newSize) / 2;
if( xPosition + widthHalf > relativeX &&
xPosition - widthHalf < relativeX &&
yPosition + heightHalf > relativeY &&
yPosition - heightHalf < relativeY) {
CatrobatDebug.info("Sprite " + this.name + " got touched");
return true;
}
return false;
}
public double getVolume() {
return volume;
}
public void setVolume(double newVolume) {
this.volume = newVolume;
}
public void playSound(String id) {
Sound sound = getSound(id);
Audio audio = sound != null ? sound.getAudio() : null;
if (audio != null) {
audio.setVolume((double) volume / 100);
audio.play();
CatrobatDebug.debug("playSound - getError: " + audio.getError() + "...");
}
else {
Stage.getInstance().log("no sound with id: " + id);
}
}
public void stopSound() {
for (Sound sound : sounds.values()) {
Audio audio = sound.getAudio();
if (audio != null) {
audio.pause();
try {
//reset
double initialTime = audio.getInitialTime();
audio.setCurrentTime(initialTime);
}
catch(JavaScriptException e) {
CatrobatDebug.debug("JavaScriptException in stopSound of sprite: " + this.name);
CatrobatDebug.debug("initialTime: " + audio.getInitialTime());
CatrobatDebug.debug(e.getMessage());
}
}
}
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
Sprite other = (Sprite) obj;
if (name == null) {
if (other.name != null)
return false;
} else if (!name.equals(other.name))
return false;
return true;
}
}